﻿using System;
using System.Collections.Generic;
using GameFramework.common;
using UnityEngine;

namespace GameFramework.timer
{
    //TODO timer的触发检测逻辑应该放到一个单独线程，回调逻辑应该在主线程里，这样提高效率，减少主线程的逻辑
    //TimerManager只负责timer的增删改查和timer触发检测，不负责回调逻辑，timerManager非主线程，则日志打印无法用debug.log等，可以设置打印回调
    //一个timerManager可能可能有多个线程，放置一个线程过多timer导致一次检测花费太多时间
    //多线程注意线程安全问题
    //1.按时间触发
    //TODO 2.按帧触发
    public class TimerManager : MonoSingleton<TimerManager>
    {
        public Dictionary<int, Timer> _Timers = new Dictionary<int, Timer>();

        //在一帧里的增，删，运行放在不同的容器，避免不必要的错误
        private HashSet<int> toRemoveTimerIds = new HashSet<int>();
        //在timerManager的update之前加的会在同一帧执行，update之后加的会在下一帧执行，可以挑战这个管理器的执行顺序，在其他帧之前执行
        private List<Timer> toAdd = new List<Timer>();
        private int timerId = 1;

        public Timer getTimer(int timerId)
        {
            if (_Timers.ContainsKey(timerId))
            {
                return _Timers[this.timerId];
            }

            return null;
        }
        /// <summary>
        /// 无时间要求
        /// 1.有次数限制(0到多次)，次数用完才结束【dota2连击刀？】
        /// 2.无次数限制。times小于0按interval触发，直到主动移除【dota2泉水，在这个范围内一直回血，无次数限制，直到出了泉水，移除回血buff】
        /// </summary>
        /// <param name="action"></param>
        /// <param name="delay"></param>
        /// <param name="times"></param>
        /// <param name="interval"></param>
        /// <returns></returns>
        public int schedule(Action<Timer> action, float delay = 0, int times = 1, float interval = 0)
        {
            return schedule(action, null, delay, times, interval, 0);
        }
        public int schedule(Action<Timer> action,Action<Timer> timerEnd, float delay = 0, int times = 1, float interval = 0)
        {
            return schedule(action, timerEnd, delay, times, interval, 0);
        }
        
        /// <summary>
        /// 有时间限制
        /// 1.有次数限制(0到多次)，则在持续时间内按interval进行触发，次数触发完了也不结束，按时间结束【比如某些活动buff，活动期间一直挂在头上，但是buff的效果次数是固定的】
        /// 2.有次数限制(0到多次)，则在持续时间内按interval进行触发，次数和时间任意一条满足就结束【熊战士的2技能，释放后，持续一段时间，能攻击固定次数，次数攻击完后效果结束，次数没用完时间到了也结束】
        /// 3.无次数限制，则在持续时间内按interval进行触发，直到时间用完就结束【dota2古法吸血和莱恩的吸蓝，有持续时间，持续时间内一直照成伤害，应该是隔零点几秒就伤害一次】
        /// </summary>
        /// <param name="action"></param>
        /// <param name="delay"></param>
        /// <param name="times"></param>
        /// <param name="interval"></param>
        /// <returns></returns>
        public int schedule(Action<Timer> action,Action<Timer> timerEnd = null, float delay = 0, int times = 1, float interval = 0,float duration = 0,bool noTimesToEnd = true)
        {
            
            if (_Timers.Count >= 100000)
            {
                Debug.LogWarning("timer数量超过10万！请检查是否正常！");
            }
            
            //TODO 放入对象池，可以重用之前的id
            Timer timer = new Timer();
            timer.timerId = timerId++;
            timer.delay = delay;
            timer.duration = duration<0?0:duration;
            timer.times = times;
            timer.noTimesToEnd = noTimesToEnd;
            timer.action = action;
            timer.timerEnd = timerEnd;
            timer.interval =interval<0?0:interval;//每帧触发
            toAdd.Add(timer);
            return timer.timerId;
        }

        public void removeTimer(int timerId)
        {
            toRemoveTimerIds.Add(timerId);
        }

        public void invokeAction(Timer timer,Action<Timer> action)
        {
            try
            {
                if (action != null)
                {
                    action.Invoke(timer);    
                }
            }
            catch (Exception e)
            {
                Debug.LogError(e);
            }
        }

        //timer有5种分支：
        //1.有持续时间限制，有次数限制(0到多次)，则在持续时间内按次数进行触发，按时间结束【比如某些活动buff，活动期间一直挂在头上，但是buff的效果次数是固定的】
        //1.有持续时间限制，有次数限制(0到多次)，则在持续时间内按次数进行触发，次数达到立即结束，未达到按时间结束【熊战士的2技能，释放后，持续一段时间，能攻击固定次数】
        //2.有持续时间限制，无次数限制(按时间间隔连续触发)，按次数触发，按时间结束【dota2古法吸血和莱恩的吸蓝，有持续时间，持续时间内一直照成伤害，应该是隔零点几秒就伤害一次】
        //====
        //4.无持续时间限制，有次数限制(0到多次)，按次数触发，按次数结束【dota2连击刀？】
        //5.无持续时间限制，无次数限制，按interval一直触发，直到buff被主动移除(dota2泉水，在这个范围内一直回血，无次数限制，直到出了泉水，移除回血buff)
        private void Update()
        {
            timerAdd();
            timerRemove();
            
            float dt = Time.deltaTime;

            //遍历timer，检查timer的状态
            foreach (Timer timer in _Timers.Values)
            {
                try
                {
                    timer.dt = dt;
                    timer.passedTime += dt;
                    if (timer.duration > 0)//有时间要求的，按时间来结束
                    {
                        bool isEnd = false;
                        //TODO interval不宜设置的太小，如果dt太大，可能连续触发几次的情况
                        if (timer.times < 0)//次数为负，表示次数无限制
                        {
                            if (timer.passedTime > (timer.delay+timer.interval * timer.hadActionTimes))
                            {
                                invokeAction(timer,timer.action);
                                timer.hadActionTimes++;
                            }
                        }
                        else
                        {
                            if (timer.passedTime > (timer.delay+timer.interval * timer.hadActionTimes) && timer.hadActionTimes<timer.times)
                            {
                                invokeAction(timer,timer.action);
                                timer.hadActionTimes++;
                            }

                            if (timer.hadActionTimes >= timer.times && timer.noTimesToEnd)//次数用完了，timer结束
                            {
                                invokeAction(timer,timer.timerEnd);
                                toRemoveTimerIds.Add(timer.timerId);
                                isEnd = true;
                            }
                        }
                        
                        if (!isEnd && timer.passedTime >= timer.duration)//有持续时间，且时间结束
                        {
                            invokeAction(timer,timer.timerEnd);
                            toRemoveTimerIds.Add(timer.timerId);
                        }
                    }
                    else//没有时间的按次数判断
                    {
                        if (timer.times < 0)//无限次数，直到timer取消
                        {
                            if (timer.passedTime > (timer.delay+timer.interval * timer.hadActionTimes) )
                            {
                                invokeAction(timer,timer.action);

                                timer.hadActionTimes++;
                            }
                        }
                        else
                        {
                            //有可能次数为0的情况，因此第二个判断不能少
                            if (timer.passedTime > (timer.delay+timer.interval * timer.hadActionTimes) && timer.hadActionTimes<timer.times)
                            {
                                invokeAction(timer,timer.action);
                                timer.hadActionTimes++;
                            }
                            if (timer.hadActionTimes >= timer.times )//触发次数完了
                            {
                                invokeAction(timer,timer.timerEnd);
                                toRemoveTimerIds.Add(timer.timerId);
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    Debug.LogError(e);
                }
            }
        }
        /// <summary>
        /// 将要移除的timer移除容器
        /// </summary>
        private void timerRemove()
        {
            foreach (int removeTimerId in toRemoveTimerIds)
            {
                _Timers.Remove(removeTimerId);
            }

            toRemoveTimerIds.Clear();
        }

        /// <summary>
        /// 将要添加的timer加入容器
        /// </summary>
        private void timerAdd()
        {
            foreach (Timer timer in toAdd)
            {
                _Timers.Add(timer.timerId, timer);
            }

            toAdd.Clear();
        }
    }
}